<!--
 @description  富文本编辑器wangEditor
 @fileName index.vue
 @author zengqiongying
 @created 2021/10/13 10:52:47
-->
<template>
  <div ref="refE" class="editor-container" :style="containerStyle" @click="disabled = true">
    <div ref="refTool" :class="['editor-head',{'disabled':disabled,'fixedTop':fixedBar}]" :style="barStyle" @click.stop="">
      <div ref="toobar" class="editor-toolbar" :class="{'tool-left':toolAlign==='left'}" />
      <slot name="toolbar" />
    </div>
    <div ref="editorBody" class="editor-body" :class="{'padding-mini':toolAlign==='left'}">
      <div class="editor-wrap-box">

        <div class="editor-other">
          <slot name="before" />
        </div>

        <div ref="editor" class="editor-content" :style="editorStyls" @click.stop="setFocusHandle" />

        <div class="editor-other">
          <slot />
        </div>

      </div>
    </div>
  </div>
</template>

<script>
import WEditor from 'wangeditor'
import extendsMenu from './extendsMenu'
import { Message } from 'element-ui'
import Cookies from 'js-cookie'
import {fetchUpload} from './apis'
export default {
  name: 'QupWangEditor',
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {
      type: String,
      default: ''
    },
    isClear: {
      type: Boolean,
      default: false
    },// 业务中我们经常会有添加操作和编辑操作，添加操作时，我们需清除上一操作留下的缓存
    fixedBar: {
      type: Boolean,
      default: false
    },
    beforeHeight: {
      type: Number,
      default: 0
    }, // slot-before的高度
    contentHeight: {
      type: Number,
      default: 0
    },
    toolAlign:{
      type:String,
      default:'center',
    }, // 工具栏位置：center 居中，left 靠左
    // 自定义配置
    customConfig: {
      type: Object,
      default: () => {
        return {
          showImgMenu: true, // 工具栏是否显示上传图片 (若影藏，则富文本内容粘贴复制图片无效)
          showMenuVideo: true, // 工具栏是否显示上传视频
          showLinkImg: true, // 工具栏是否显示隐藏网络图片，默认true
          uploadImgServer: ``, // 上传图片服务器地址
          uploadFileName: 'file', // 上传图片参数名称
          uploadImgHeaders: {}, // 上传图片请求头参数
          uploadImgMaxSize: 5, // 限制图片大小
          uploadImgMaxLength: 100, // 一次最多上传图片数量
          uploadImgParams: {}, // 自定义图片上传参数
          customImgField:'fileUrl', // 自定义上传uploadCustom时插入图片时使用的图片地址字段名称
          
          uploadVideoServer: ``, // 上传视频服务器地址
          uploadVideoName: 'file',
          uploadVideoHeaders: {}, // 上传视频请求头参数
          uploadVideoParams: {}, // 自定义视频上传参数
          customVideoField:'fileUrl', // 自定义上传视频时插入视频时使用的视频地址字段名称
        }
      }
    }
  },
  data() {
    return {
      disabled: false,
      editor: null,
      info_: null,
      barTop: 0,
      barHeight: 0
    }
  },
  computed: {
    containerStyle() {
      const styl = {}
      if (this.fixedBar) {
        styl.height = '100%'
        if (this.barHeight) {
          styl['padding-top'] = `${this.barHeight}px`
        }
      }
      return styl
    },
    barStyle() {
      const styl = {}
      if (this.fixedBar) {
        styl['top'] = `${this.barTop || 0}px`
      }
      return styl
    },
    editorStyls() {
      const styl = {}
      if (this.fixedBar) {
        const clientHeight = document.documentElement.clientHeight // this.$refs.editorContainer.offsetHeight
        const minEHeight = clientHeight - this.beforeHeight - this.barTop - this.barHeight - 16 // 16 = editorBody.padding-top值
        styl['min-height'] = minEHeight + 'px'
      } else {
        styl['height'] = this.contentHeight + 'px'
      }
      return styl
    }
  },
  watch: {
    isClear(val) {
      // console.log(val)
      if (val) {
        this.editor && this.editor.txt.clear()
        this.info_ = null
      }
    },
    value(newVal, oldVal) {
      if (newVal !== this.editor.txt.html()) {
        this.editor && this.editor.txt.html(newVal)
      }
      // 光标移到最后
      // this.editor.selection.moveCursor(this.editor.$textElem.elems[0],false);
    },
    // Watch disabled change
    disabled(newVal, oldVal) {
      /* if (newVal) {
        this.editor.disable()
      } else {
        this.editor.enable()
      } */
    }
  },
  mounted() {
    this.initEditor()
    this.$nextTick(() => {
      this.barHeight = this.$refs.refTool.offsetHeight
      this.barTop = this.$refs.refE.getBoundingClientRect().top
    })
  },
  beforeDestroy() {
    // 调用销毁 API 对当前编辑器实例进行销毁
    this.editor.destroy()
    this.editor = null
  },
  methods: {
    initEditor() {
      /*
        this.editor = new WangEditor ('#e') // 菜单和编辑区域不分离
        this.editor = new WEditor('#e_toolbar', '#e_content') // 菜单和编辑区域分离
      */
      this.editor = new WEditor(this.$refs.toobar, this.$refs.editor)
      // console.log(this.editor)
      this.editor.config.zIndex = 1
      this.editor.config.onchangeTimeout = 1000 // 单位 ms
      // 自定义alert
      this.editor.config.customAlert = (s, t) => {
        switch (t) {
          case 'success':
            Message.success(s)
            break
          case 'info':
            Message.info(s)
            break
          case 'warning':
            Message.warning(s)
            break
          case 'error':
            Message.error(s)
            break
          default:
            Message.info(s)
            break
        }
      }
      // 注册菜单
      for (const key in extendsMenu) {
        this.editor.menus.extend(key, extendsMenu[key]) // 清除格式
      }
      // 配置行高
      this.editor.config.lineHeights = ['1', '1.2', '1.5', '2', '2.5', '3']
      // 配置字号
      this.editor.config.fontSizes = {
        'x-small': { name: '12px', value: '1' },
        'small': { name: '14px', value: '2' },
        'normal': { name: '16px', value: '3' },
        'large': { name: '18px', value: '4' },
        'x-large': { name: '24px', value: '5' },
        'xx-large': { name: '32px', value: '6' },
        'xxx-large': { name: '48px', value: '7' }
      }
      // 配置菜单
      this.editor.config.menus = [
        'undo', // 撤销
        'redo', // 重复
        'clearFormat', // 清除格式-自定义
        'head', // 标题
        'fontSize', // 字号
        'fontName', // 字体
        'bold', // 粗体
        'italic', // 斜体
        'underline', // 下划线
        'strikeThrough', // 删除线
        /* 'foreColor', // 文字颜色
        'backColor', // 背景颜色 */
        'fontColor', // 文字颜色-自定义
        'backgroudColor', // 背景颜色-自定义
        'list', // 列表
        'todo', // 待办事项
        'justify', // 对齐方式
        'indent', // 缩进
        'lineHeight', // 行高
        'link', // 插入链接
        'quote', // 引用
        'emoticon', // 表情
        this.customConfig.showImgMenu !== false && 'image', // 插入图片
        'table', // 表格
        this.customConfig.showMenuVideo !== false && 'video', // 插入视频
        'splitLine', // 分割线
        'code'// 插入代码
      ]
      // 菜单栏提示为下标
      this.editor.config.menuTooltipPosition = 'down'

      this.editor.config.onchange = (html) => {
        this.info_ = html // 绑定当前逐渐地值
        this.$emit('change', this.info_) // 将内容同步到父组件中
      }
      // 监听blur事件
      /* this.editor.config.onblur = (html) => {

      } */

      // this.editor.config.showFullScreen = true
      const uploadHeaders = {
        'X-Token':Cookies.get('Admin-Token') ? Cookies.get('Admin-Token'): Cookies.get('QUP-COMPONENT-TOKEN') || '',
        'Authorization': Cookies.get('Admin-Token') ? `Bearer ${Cookies.get('Admin-Token')}` : Cookies.get('QUP-COMPONENT-TOKEN')?`Bearer ${Cookies.get('QUP-COMPONENT-TOKEN')}`:''
      }

      for (const key in this.customConfig) {
        this.editor[key] = this.customConfig[key]
      }

      this.editor.config.showLinkImg = true // 隐藏网络图片
      this.editor.config.uploadFileName = this.customConfig.uploadFileName || 'file'
      this.editor.config.uploadImgHeaders = this.customConfig.uploadImgHeaders || uploadHeaders
      this.editor.config.uploadImgServer = this.customConfig.uploadImgServer

      this.editor.config.uploadImgHooks = {
        before: function(xhr, editor, files) {
          // 图片上传之前触发
          // xhr 是 XMLHttpRequst 对象，editor 是编辑器对象，files 是选择的图片文件

          // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传
          // return {
          //     prevent: true,
          //     msg: '放弃上传'
          // }
        },
        success: function(xhr, editor, result) {
          // 图片上传并返回结果，图片插入成功之后触发
          // xhr 是 XMLHttpRequst 对象，editor 是编辑器对象，result 是服务器端返回的结果
        },
        fail: function(xhr, editor, result) {
          // 图片上传并返回结果，但图片插入错误时触发
          // xhr 是 XMLHttpRequst 对象，editor 是编辑器对象，result 是服务器端返回的结果
        },
        error: function(xhr, editor) {
          // 图片上传出错时触发
          // xhr 是 XMLHttpRequst 对象，editor 是编辑器对象
        },
        timeout: function(xhr, editor) {
          // 图片上传超时时触发
          // xhr 是 XMLHttpRequst 对象，editor 是编辑器对象
        },

        // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式，可使用该配置
        // （但是，服务器端返回的必须是一个 JSON 格式字符串！！！否则会报错）
        customInsert: function(insertImg, result, editor) {
          // 图片上传并返回结果，自定义插入图片的事件（而不是编辑器自动插入图片！！！）
          // insertImg 是插入图片的函数，editor 是编辑器对象，result 是服务器端返回的结果

          // 举例：假如上传图片成功后，服务器端返回的是 {url:'....'} 这种格式，即可这样插入图片：
          var url = result.data && result.data.fileUrl ? result.data.fileUrl : ''
          insertImg(url)

          // result 必须是一个 JSON 格式字符串！！！否则报错
        }
      }

      // 自定义图片上传(粘贴剪截图片自动上传)
      this.editor.config.customUploadImg = (resultFiles, insertImgFn) => {
        // 粘贴图片时的回调
        // resultFiles 粘贴的图片的基本信息
        // insertImgFn 图片上传到七牛云后对图片进行回显的回调函数
        resultFiles.forEach((element) => {
          // 循环对粘贴图片执行上传回显功能
          this.uploadCustom(element, insertImgFn)
        })
      }

      // 自定义上传视频
      /* this.editor.config.customUploadVideo = (resultFiles, insertVideoFn) => {
        // resultFiles 是 input 中选中的文件列表
        // insertVideoFn 是获取视频 url 后，插入到编辑器的方法

        // 上传视频，返回结果，将视频地址插入到编辑器中
        // insertVideoFn(videoUrl)
      } */

      this.editor.config.showLinkVideo = false // 隐藏网络视频
      this.editor.config.uploadVideoName = 'file'
      this.editor.config.uploadVideoHeaders = this.customConfig.uploadVideoHeaders || uploadHeaders
      this.editor.config.uploadVideoServer = this.customConfig.uploadVideoServer
      this.editor.config.uploadVideoHooks = {
        customInsert: (insertVideoFn, result)=> {
          // result 必须是一个 JSON 格式字符串！！！否则报错
          const keyName = this.customConfig.customVideoField || 'fileUrl'
          if(result.data && result.data[keyName]){
            const url = result.data && result.data[keyName]
            insertVideoFn(url)
          }else{
            console.log('视频地址所属字段有误')
          }
        }
      }
      this.editor.create()
      this.editor.txt.html(this.value)
    },
    setFocusHandle() {
      this.disabled = false
    },
    // 通过代码获取编辑器内容
    getEditorData() {
      const data = this.editor.txt.html()
      return data
    },
    // 保存光标位置
    saveSelection() {
      this.editor && this.editor.selection.saveRange()
    },
    // 恢复选区
    restoreSelection() {
      this.editor && this.editor.selection.restoreSelection()
    },
    /**
     * 插入内容
     * @param {string} content 内容/文件地址
     * @param {string} type 内容类型： image 图片, video 视频，audio 音频
     * */
    insertContent(content,type) {
      if (type === 'image') {
        this.editor.cmd.do('insertHTML', `<img src="${content}" style="max-width:100%;"/>`)
      } else if (type === 'video') {
        this.editor.cmd.do('insertHTML', `<video src="${content}" 
            webkit-playsinline="true" 
            x5-video-player-type="h5" 
            x5-video-orientation="portraint" 
            controls="controls" style="max-width:100%"></video><p data-we-empty-p=""><br></p>`)
      } else if (type === 'audio') {
        this.editor.cmd.do('insertHTML', `<audio src="${content}" controls="controls" style="max-width:100%"></audio><p data-we-empty-p=""><br></p>`)
      } else {
        this.editor.cmd.do('insertHTML', `${content}`)
      }
    },
    // 自定义上传
    uploadCustom(element, insertImgFn) {
      const formData = new FormData()
      const name = this.editor.config.uploadFileName
      formData.append(name, element)
      fetchUpload({
        url: this.customConfig.uploadImgServer,
        method: 'post',
        data: formData
      }).then(res => {
        const keyName = this.customConfig.customImgField || 'fileUrl'
        const url = res.data && res.data[keyName] || ''
        insertImgFn(url)
      })
    },
    // 重新加载粘贴文本的图片地址
    reloadPasteImage(imgs) {
      const textIMGs = this.$refs.editor.querySelectorAll('img')
      textIMGs.forEach(el => {
        const url = el.src
        if (imgs.includes(url)) {
          this.coverImgToBase64(url, (base64) => {
            console.log('base64', base64)
            el.src = base64
          })
        }
      })
    },
    // 图片地址转base64编码
    coverImgToBase64(url, callback) {
      var canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const image = new Image()
      image.crossOrigin = ''
      image.src = url
      image.onload = function() {
        console.log(url, '加载完成')
        canvas.width = image.width
        canvas.height = image.height
        ctx.drawImage(image, 0, 0, image.width, image.height)
        const ext = image.src.substring(image.src.lastIndexOf('.') + 1).toLowerCase()
        const dataURL = canvas.toDataURL('image/' + ext)
        callback.call(this, dataURL)
        canvas = null
      }
      image.οnerrοr = function() {
        console.log('图片加载失败', url)
      }
    }
  }
}

function getBase64Image(img, callback) { // 传入图片路径，返回base64
  const toBase64 = (img, width, height) => {
    // width、height调用时传入具体像素值，控制大小 ,不传则默认图像大小
    let canvas = document.createElement('canvas')
    canvas.width = width || img.width
    canvas.height = height || img.height

    const ctx = canvas.getContext('2d')
    ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
    var ext = img.src.substring(img.src.lastIndexOf('.') + 1).toLowerCase()
    var dataURL = canvas.toDataURL('image/' + ext)
    canvas = null
    return dataURL
  }
  return new Promise((resolve, reject) => {
    const image = new Image()
    image.crossOrigin = ''
    image.src = img
    if (img) {
      image.onload = function() {
        const base64 = toBase64(image)
        resolve(base64)
      }
    }
  })
}
</script>
<style lang='scss' scoped>
$mainColor: #3DC094;
.editor-container{
  position: relative;
  background: #f6f8f9;
  box-sizing: border-box;
}
.editor-head{
  min-height: 42px;
  background-color: #f6f8f9;
  z-index: 1;
  &.fixedTop{
    position: fixed;
    left: 0;
    right: 0;
    top: 0;
  }
  &.disabled{
    color: #595959;
    opacity: 0.5;
    cursor:not-allowed;
    &::after{
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom:0;
      height: 40px;
      z-index: 9999;
      background: rgba($color: #FFF, $alpha: .5);
      cursor:not-allowed;
    }
  }
}
.editor-toolbar{
  width: 100%;
  position: relative;
  min-height: 42px;
  z-index: 1;
  display: flex;
  justify-content: center;
  background-color: #f6f8f9;
  border: 1px solid #E9E9E9;
  border-width: 1px 0;
  box-sizing: border-box;
  color: #333;
  &.tool-left{
    justify-content: left;
  }
}
.editor-body{
  position: relative;
  z-index: 0;
  padding: 16px 60px;
  .editor-other{
    padding: 0 20px;
  }
  .editor-wrap-box{
    position: relative;
    min-height: 100%;
    border: 1px solid #e8e8e8;
    // box-shadow: 0 2px 8px rgba(115,115,115,.3);
    background-color: #fff;
    display: flex;
    flex-direction:column;
  }
  .editor-content{
    position: relative;
    padding:20px 10px;
  }
  &.padding-mini{
    padding:10px;
    .editor-content{
      padding:10px;
    }
  }
}
.editor-head.fixedTop~.editor-body{
  min-height: 100%;
  box-sizing: border-box;
}
// 字号菜单下拉宽度设置
/deep/ .w-e-toolbar{
  .w-e-menu:nth-child(5){
    .w-e-droplist{
      width:70px !important
    }
  }
}
</style>
